/*
 * ManagerImpl.h
 *
 *  Created on: 2011/07/02
 *      Author: naoki
 */

#ifndef INCLUDED_NTENGINE_INPUT_MANAGERIMPL_H
#define INCLUDED_NTENGINE_INPUT_MANAGERIMPL_H

#include "NTEngine/Base/List.h"
#include "NTEngine/Input/Manager.h"
#include "NTEngine/Input/Keyboard.h"

#include "Base/src/android_app.h"
#include <android/native_activity.h>

namespace NTEngine {
namespace Input {

class ManagerImpl {
public:
	// -----------------------------------
	ManagerImpl( android_app* app )
	: mApp( app )
	, mDoubleBufferIndex( 0 ) {
		for ( int i = 0; i < MAX_KEYCODE_COUNT; ++i ) {
			// ダブルバッファを一度に初期化
			mKeys[ 0 ][ i ] = false;
			mKeys[ 1 ][ i ] = false;
			// 負数は無効な値
			mKeyEvents[ i ] = -1;
		}
		for ( int i = 0; i < MAX_POINTER_COUNT; ++i ) {
			// 16もあれば十分だと思うが...
			mTouchEvents[ i ].setCapacity( 16 );
		}
	}

	~ManagerImpl() {
		// 入力用コールバックをはずしておく
		mApp->onInputEvent = 0;
		mApp = 0;
	}

	// このメソッドはメインループのLooperで実行される？
	int32_t onHandleEvent( AInputEvent* event ) {
		int32_t type = AInputEvent_getType( event );
		// eventの分析
		if ( type == AINPUT_EVENT_TYPE_MOTION ) {
			const int ptrCount = AMotionEvent_getPointerCount( event );
			// 新しい処理
			for ( int i = 0; i < ptrCount; ++i ) {
				int32_t pid = AMotionEvent_getPointerId( event, i );
				if ( MAX_POINTER_COUNT <= pid ) {
					continue; // サポート以上のタッチがきた。スルーする
				}
				TouchEventList& list = mTouchEvents[ pid ];
				bool added = false;
				int pos = list.last();
				if ( !list.isEnd( pos ) ) {
					TouchEvent*e = list.value( pos );
					if (
						( e->action == AMOTION_EVENT_ACTION_MOVE ) &&
						( AMotionEvent_getAction( event ) == AMOTION_EVENT_ACTION_MOVE )
					) {
						e->set( event, i );
						added = true;
					}
				}
				if ( !added ) {
					list.addTail( TouchEvent( event, i ) );
				}
			}
			return 1;
		} else if ( type == AINPUT_EVENT_TYPE_KEY ) {
			int32_t keycode = AKeyEvent_getKeyCode( event );
			mKeyEvents[ keycode ] = AKeyEvent_getAction( event );
#if 0 // ある程度動作するまできっておく
			return 1;
#endif
		}
		// デフォルト
		return 0;
	}

	void update() {
		// バッファ切り替え
		int prev = mDoubleBufferIndex;
		mDoubleBufferIndex = 1 - mDoubleBufferIndex;
		// タッチイベント処理
		for ( int i = 0; i < MAX_POINTER_COUNT; ++i ) {
			TouchStatus& s = mTouchStatus[ i ];
			TouchEventList& list = mTouchEvents[ i ];
			int pos = list.first();
			// 先頭が終端なら空だ
			if ( list.isEnd( pos ) ) {
				// ここでfalseを入れていないとだめだ
				// イベントがなければ基本的に継続でいいがこれだけはクリアする必要がある
				s.tapped = false;
				continue;
			}
			TouchEvent* e = list.value( pos );
			// 先頭のイベントを処理する
			if (
				( e->action == AMOTION_EVENT_ACTION_DOWN ) ||
				( e->action == AMOTION_EVENT_ACTION_POINTER_DOWN )
			) {
				// タッチした瞬間
				s.down = true;
				s.tapped = true;
				s.startX = e->x;
				s.startY = e->y;
			} else {
				// タッチ中
				s.tapped = false;
			}
			if (
				( e->action == AMOTION_EVENT_ACTION_UP ) ||
				( e->action == AMOTION_EVENT_ACTION_POINTER_UP )
			) {
				// 離した
				s.down = false;
			}
			// 他
			s.lastX = e->x;
			s.lastY = e->y;
			s.size = e->size;

			// 先頭の次が終端（sizeが1）じゃない、もしくはactionがMOVEじゃないなら先頭を削除
			if (
				!list.isEnd( list.next( pos ) ) ||
				e->action != AMOTION_EVENT_ACTION_MOVE
			) {
				list.removeHead();
			}
		}
		// キーイベント処理
		for ( int i = 0; i < MAX_KEYCODE_COUNT; ++i ) {
			int32_t action = mKeyEvents[ i ];
			mKeyEvents[ i ] = -1; // 無効値を入れておく
			if ( action >= 0 ) {
				mKeys[ mDoubleBufferIndex ][ i ] = ( action != AKEY_EVENT_ACTION_UP );
			} else {
				mKeys[ mDoubleBufferIndex ][ i ] = mKeys[ prev ][ i ];
			}
		}
	}

	// ----------------------------------------
	float getMotionEventX( int pid ) const {
		ASSERT( 0 <= pid && pid < MAX_POINTER_COUNT );
		return mTouchStatus[ pid ].lastX;
	}

	// ----------------------------------------
	float getMotionEventY( int pid ) const {
		ASSERT( 0 <= pid && pid < MAX_POINTER_COUNT );
		return mTouchStatus[ pid ].lastY;
	}

	// -------------------------------------------
	float getMotionEventSize( int pid ) const {
		ASSERT( 0 <= pid && pid < MAX_POINTER_COUNT );
		return mTouchStatus[ pid ].size;
	}

	// -------------------------------------------
	bool isMotionEventOn( int pid ) const {
		ASSERT( 0 <= pid && pid < MAX_POINTER_COUNT );
		return mTouchStatus[ pid ].down;
	}

	// -------------------------------------------
	bool isMotionEventTriggered( int pid ) const {
		return isMotionEventOn( pid ) && mTouchStatus[ pid ].tapped;
	}

	// -------------------------------------------
	bool isTapped() const {
		for ( int i = 0; i < MAX_POINTER_COUNT; ++ i ) {
			if ( mTouchStatus[ i ].tapped ) {
				return true;
			}
		}
		return false;
	}

	// -------------------------------------------
	int getTappedPointer() const {
		for ( int i = 0; i < MAX_POINTER_COUNT; ++ i ) {
			if ( mTouchStatus[ i ].tapped ) {
				return i;
			}
		}
		return -1;
	}

	// -------------------------------------------
	float getMotionEventStartX( int pid ) const {
		ASSERT( 0 <= pid && pid < MAX_POINTER_COUNT );
		return mTouchStatus[ pid ].startX;
	}

	// -------------------------------------------
	float getMotionEventStartY( int pid ) const {
		ASSERT( 0 <= pid && pid < MAX_POINTER_COUNT );
		return mTouchStatus[ pid ].startY;
	}

	// -------------------------------------------
	float getMotionEventDistanceX( int pid ) const {
		ASSERT( 0 <= pid && pid < MAX_POINTER_COUNT );
		return mTouchStatus[ pid ].lastX - mTouchStatus[ pid ].startX;
	}

	// -------------------------------------------
	float getMotionEventDistanceY( int pid ) const {
		ASSERT( 0 <= pid && pid < MAX_POINTER_COUNT );
		return mTouchStatus[ pid ].lastY - mTouchStatus[ pid ].startY;
	}

	// -------------------------------------------
	bool isKeyOn( int i ) const {
		ASSERT( 0 <= i && i < MAX_KEYCODE_COUNT );
		int k = convertKeyID( i );
		return mKeys[ mDoubleBufferIndex ][ k ];
	}

	// -------------------------------------------
	bool isKeyTriggered( int i ) const {
		ASSERT( 0 <= i && i < MAX_KEYCODE_COUNT );
		int k = convertKeyID( i );
		return mKeys[ mDoubleBufferIndex ][ k ] && !mKeys[ 1 - mDoubleBufferIndex ][ k ];
	}

	// -------------------------------------------
	static int convertKeyID( int a ) {
		typedef Keyboard K;
		int r = 0;
		if ( a >= 'a' && a <= 'z' ) { //大文字に変換
			a = 'A' + ( a - 'a' );
		}
		bool isAlpha = ( a >= 'A' && a <= 'Z' );
		bool isNum = ( a >= '0' && a <= '9' );
		if ( isAlpha ) {
			a -= 'A' - AKEYCODE_A;
			r = a;
		} else if ( isNum ) {
			a -= '0' - AKEYCODE_0;
			r = a;
		}
		if ( r == 0 ){
			switch ( a ) {
				case ' ': r = AKEYCODE_SPACE; break;
				case '+': r = AKEYCODE_PLUS; break;
				case ',': r = AKEYCODE_COMMA; break;
				case '-': r = AKEYCODE_MINUS; break;
				case '.': r = AKEYCODE_PERIOD; break;
				case K::KEY_RETURN: r = AKEYCODE_ENTER; break;
				case K::KEY_TAB: r = AKEYCODE_TAB; break;
				case K::KEY_ESC: r = 0; break;
				case K::KEY_SHIFT: r = AKEYCODE_SHIFT_LEFT; break;
				case K::KEY_CONTROL: r = 0; break;
				case K::KEY_ALT: r = AKEYCODE_ALT_LEFT; break;
				case K::KEY_F1: r = 0; break;
				case K::KEY_F2: r = 0; break;
				case K::KEY_F3: r = 0; break;
				case K::KEY_F4: r = 0; break;
				case K::KEY_F5: r = 0; break;
				case K::KEY_F6: r = 0; break;
				case K::KEY_F7: r = 0; break;
				case K::KEY_F8: r = 0; break;
				case K::KEY_F9: r = 0; break;
				case K::KEY_F10: r = 0; break;
				case K::KEY_F11: r = 0; break;
				case K::KEY_F12: r = 0; break;
				case K::KEY_UP: r = AKEYCODE_DPAD_UP; break;
				case K::KEY_DOWN: r = AKEYCODE_DPAD_DOWN; break;
				case K::KEY_LEFT: r = AKEYCODE_DPAD_LEFT; break;
				case K::KEY_RIGHT: r = AKEYCODE_DPAD_RIGHT; break;
				case K::KEY_NUM0: r = AKEYCODE_0; break;
				case K::KEY_NUM1: r = AKEYCODE_1; break;
				case K::KEY_NUM2: r = AKEYCODE_2; break;
				case K::KEY_NUM3: r = AKEYCODE_3; break;
				case K::KEY_NUM4: r = AKEYCODE_4; break;
				case K::KEY_NUM5: r = AKEYCODE_5; break;
				case K::KEY_NUM6: r = AKEYCODE_6; break;
				case K::KEY_NUM7: r = AKEYCODE_7; break;
				case K::KEY_NUM8: r = AKEYCODE_8; break;
				case K::KEY_NUM9: r = AKEYCODE_9; break;
			}
		}
		return r;
	}

	void operator=( const ManagerImpl& ); // 禁止

	static const int MAX_POINTER_COUNT = 4; // だいたいこれくらい
	static const int MAX_KEYCODE_COUNT = 128; // キーコード少なめ
	struct TouchEvent {
		float x, y, size;
		int32_t action;
		TouchEvent() : x( 0 ), y( 0 ), size( 0 ), action( -1 ) {
		}
		TouchEvent( const AInputEvent* event, int i ) {
			set( event, i );
		}
		void set( const AInputEvent* event, int i ) {
			x = AMotionEvent_getX( event, i );
			y = AMotionEvent_getY( event, i );
			size = AMotionEvent_getSize( event, i );
			int32_t act = AMotionEvent_getAction( event );
			int32_t pid = ( act & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK ) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
			if (
				( pid == AMotionEvent_getPointerId( event, i ) ) ||
				( act == AMOTION_EVENT_ACTION_DOWN ) ||
				( act == AMOTION_EVENT_ACTION_UP )
			) {
				action = act & AMOTION_EVENT_ACTION_MASK;
			} else {
				action = AMOTION_EVENT_ACTION_MOVE;
			}
		}
	};
	typedef List< TouchEvent > TouchEventList;
	struct TouchStatus {
		bool down, tapped;
		float startX, startY, lastX, lastY, size;
		TouchStatus() : down( false ), tapped( false ), startX( 0 ), startY( 0 ), lastX( 0 ), lastY( 0 ), size( 0 ) {
		}
	};

	android_app* mApp;
	// 参照用
	TouchStatus mTouchStatus[ MAX_POINTER_COUNT ];
	bool mKeys[ 2 ][ MAX_KEYCODE_COUNT ];
	int mDoubleBufferIndex;
	// イベントを格納する用
	TouchEventList mTouchEvents[ MAX_POINTER_COUNT ];
	int32_t mKeyEvents[ MAX_KEYCODE_COUNT ];
};

extern ManagerImpl* gManagerImpl;

} // namespace Input
} // namespace NTEngine

#endif // INCLUDED_NTENGINE_INPUT_MANAGERIMPL_H
